/*
 * Copyright 2022-2025 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.instancio.feed;

import org.instancio.documentation.ExperimentalApi;
import org.instancio.generator.Generator;
import org.instancio.generator.GeneratorSpec;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.function.Function;

/**
 * Defines annotations that can be used with {@link FeedSpec} methods.
 *
 * @since 5.0.0
 */
@ExperimentalApi
public interface FeedSpecAnnotations {

    /**
     * Annotation used to specify the mapping between a method in
     * a {@link Feed} and a property in an external data source.
     * This allows methods in a feed interface to be explicitly
     * associated with properties by name.
     *
     * <p>The {@link AliasSpec} annotation should be placed on methods
     * within a {@link Feed} interface that return {@link FeedSpec}, enabling
     * explicit binding of the method to a named data source property.
     *
     * @since 5.0.0
     */
    @ExperimentalApi
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface AliasSpec {

        /**
         * Specifies the name of the property in the external data source
         * to which the annotated method is explicitly mapped.
         *
         * @return property name defined in an external data source
         * @since 5.0.0
         */
        @ExperimentalApi
        String value();
    }

    /**
     * A spec that is derived from one or more other specs.
     *
     * @see Feed
     * @since 5.0.0
     */
    @ExperimentalApi
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface FunctionSpec {

        /**
         * Specifies the names of one or more {@link FeedSpec}
         * methods declared by a feed.
         *
         * @return one or more method names that will be used
         * to provide arguments to the target function
         * @since 5.0.0
         */
        @ExperimentalApi
        String[] params();

        /**
         * A class containing the method for handling this function spec.
         *
         * @return a class that declares a single method for handling
         * the function spec.
         * @since 5.0.0
         */
        @ExperimentalApi
        Class<? extends FunctionProvider> provider();
    }

    /**
     * A feed spec that is generated by a {@link Generator}.
     *
     * @see Feed
     * @since 5.0.0
     */
    @ExperimentalApi
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface GeneratedSpec {

        /**
         * The generator class for producing data.
         * Note that the specified class must implement the
         * {@link Generator} interface.
         *
         * @return the generator class
         * @since 5.0.0
         */
        @ExperimentalApi
        Class<? extends GeneratorSpec<?>> value();
    }

    /**
     * Specifies that the annotated spec method
     * can generate {@code null} values.
     *
     * @see Feed
     * @since 5.0.0
     */
    @ExperimentalApi
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface NullableSpec {
    }

    /**
     * A template spec is for producing string values
     * based on a string template with inputs provided by other specs.
     *
     * @see Feed
     * @since 5.0.0
     */
    @ExperimentalApi
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface TemplateSpec {

        /**
         * Specifies a string template that uses <b>{@code ${}}</b>
         * syntax for placeholders. The placeholder refers to
         * spec properties defined by a feed.
         *
         * <p>Example:
         * <pre>{@code
         * @Feed.Source(resource = "sample.csv")
         * interface SampleFeed extends Feed {
         *
         *     FeedSpec<String> firstName();
         *
         *     FeedSpec<String> lastName();
         *
         *     @TemplateSpec("Hello ${firstName} ${lastName}!")
         *     FeedSpec<String> greeting();
         * }
         * }</pre>
         *
         * @since 5.0.0
         */
        @ExperimentalApi
        String value();
    }

    /**
     * Annotation to specify a custom {@link Function} to be used for
     * converting string values to a specified type.
     *
     * <p>If a spec method is annotated with both, {@link WithStringMapper}
     * and {@link WithPostProcessor}, the mapping is done before
     * applying the post-processors.
     *
     * <p>This annotation is ignored when used with:
     *
     * <ul>
     *   <li>{@link FunctionSpec}</li>
     *   <li>{@link GeneratedSpec}</li>
     *   <li>{@link TemplateSpec}</li>
     * </ul>
     *
     * @since 5.0.0
     */
    @ExperimentalApi
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface WithStringMapper {

        /**
         * Specifies the function class to be used for string conversions.
         *
         * @return the mapper class
         * @since 5.0.0
         */
        @ExperimentalApi
        Class<? extends Function<String, ?>> value();
    }

    /**
     * This annotation allows specifying one or more
     * {@link PostProcessor} classes for processing
     * values returned by the annotated spec method.
     *
     * @see PostProcessor
     * @see Feed
     * @since 5.0.0
     */
    @ExperimentalApi
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @interface WithPostProcessor {

        /**
         * One or more classes implementing the {@link PostProcessor}.
         *
         * @return post processor class(es)
         * @since 5.0.0
         */
        @ExperimentalApi
        Class<? extends PostProcessor<?>>[] value();
    }
}
